﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using msilgc.cbuilder;
using msilgc.cbuilder.CSyntaxTree;
using msilgc.common;
using msilgc.common.Attributes;

namespace msilgc.runtime
{
    internal sealed class DefaultMethodHandle : BaseMethodHandle
    {
        private readonly MethodInfo _method;

        public DefaultMethodHandle(MethodInfo method)
        {
            _method = method;
        }

        public override void Compile()
        {
            Compiler compiler = new Compiler();
            ICMethodBody methodBody = compiler.TranslateMethod(_method);
            StringBuilder code = new StringBuilder(4096);
            methodBody.Emit(code, new DefaultMethodNameMapper());
            GeneratedKernelCode = code.ToString();
            CompilerMessages = "";
        }

        protected override void Invoke(Grain threadDim, Grain blockDim, params object[] args)
        {
            if(string.IsNullOrEmpty(GeneratedKernelCode))
            {
                Compile();
            }
            Queue<ThreadArgs> threadArgs = new Queue<ThreadArgs>();
            for(int i = 0; i < threadDim.X; i++)
            {
                for(int j = 0; j < threadDim.Y; j++)
                {
                    for(int k = 0; k < threadDim.Z; k++)
                    {
                        threadArgs.Enqueue(new ThreadArgs(new Grain(i, j, k), blockDim, args));
                    }
                }
            }
            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < System.Environment.ProcessorCount; i++ )
            {
                Thread thread = new Thread(InvokeThreadPool);
                thread.Start(threadArgs);
                threads.Add(thread);
            }
            foreach (Thread thread in threads)
            {
                thread.Join();
            }
        }

        private static IEnumerable<ThreadArgs> AtomicDequeue(Queue<ThreadArgs> queue)
        {
            List<ThreadArgs> work = new List<ThreadArgs>();
            lock(queue)
            {
                if(queue.Any())
                {
                    work.Add(queue.Dequeue());
                }
            }
            return work;
        }

        private void InvokeThreadPool(object args)
        {
            Queue<ThreadArgs> threadArgs = (Queue<ThreadArgs>) args;
            IEnumerable<ThreadArgs> work;
            while((work = AtomicDequeue(threadArgs)).Any())
            {
                foreach (ThreadArgs arg in work)
                {
                    InvokeThread(arg);
                }
            }
        }

        private void InvokeThread(ThreadArgs args)
        {
            Grain blockIdx = new Grain(0, 0, 0);
            object[] parametersArray = GetArgArray(args, _method, ref blockIdx);
            int blockIdxOffset = GetBlockIdxOffset(_method);
            for(int i = 0; i < args.BlockDim.X; i++)
            {
                blockIdx.X = i; 
                for(int j = 0; j < args.BlockDim.Y; j++)
                {
                    blockIdx.Y = j;
                    for(int k = 0; k < args.BlockDim.Z; k++)
                    {
                        blockIdx.Z = k;
                        UpdateBlockIdx(blockIdxOffset, blockIdx, parametersArray);
                        _method.Invoke(null, parametersArray);
                    }
                }
            }
        }

        private static void UpdateBlockIdx(int offset, Grain blockIdx, object[] parameters)
        {
            if(offset != (-1))
            {
                parameters[offset] = blockIdx;
            }
        }

        private static int GetBlockIdxOffset(MethodInfo method)
        {
            int i = (-1);
            foreach (var parameter in method.GetParameters())
            {
                ++i;
                if (parameter.GetCustomAttributes(typeof(BlockIdxParameter), true).Length > 0)
                {
                    return i;
                }
            }
            return (-1);
        }

        private static object[] GetArgArray(ThreadArgs args, MethodInfo method, ref Grain blockIdx)
        {
            Queue<object> passed = new Queue<object>(args.MethodArgs);
            var parameterArray = method.GetParameters();
            List<object> parameters = new List<object>(parameterArray.Length);
            foreach (var parameter in parameterArray)
            {
                if (parameter.GetCustomAttributes(typeof(ThreadIdxParameter), true).Length > 0)
                {
                    parameters.Add(args.ThreadIdx);
                }
                else if (parameter.GetCustomAttributes(typeof(BlockIdxParameter), true).Length > 0)
                {
                    parameters.Add(blockIdx);
                }
                else if (parameter.GetCustomAttributes(typeof(BlockDimParameter), true).Length > 0)
                {
                    parameters.Add(args.BlockDim);
                }
                else
                {
                    parameters.Add(passed.Dequeue());
                }
            }
            return parameters.ToArray();
        }

        private class ThreadArgs
        {
            public Grain ThreadIdx { get; private set; }
            public Grain BlockDim { get; private set; }
            public object[] MethodArgs { get; private set; }
            public ThreadArgs(Grain threadIdx, Grain blockDim, object[] args)
            {
                ThreadIdx = threadIdx;
                BlockDim = blockDim;
                MethodArgs = args;
            }
        }

        private class DefaultMethodNameMapper : IMethodNameMapper
        {
            public string MapMethodToName(MethodBase method)
            {
                return method.Name;
            }
        }
    }
}
